package com.teamide.util;

import java.io.File;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.net.URLStreamHandler;
import java.nio.charset.Charset;
import java.util.Map;
import java.util.Map.Entry;

/**
 * 统一资源定位符相关工具类
 * 
 * 
 * 
 */
public class URLUtil {

	/** 针对ClassPath路径的伪协议前缀（兼容Spring）: "classpath:" */
	public static final String CLASSPATH_URL_PREFIX = "classpath:";
	/** URL 前缀表示文件: "file:" */
	public static final String FILE_URL_PREFIX = "file:";
	/** URL 前缀表示jar: "jar:" */
	public static final String JAR_URL_PREFIX = "jar:";
	/** URL 前缀表示war: "war:" */
	public static final String WAR_URL_PREFIX = "war:";
	/** URL 协议表示文件: "file" */
	public static final String URL_PROTOCOL_FILE = "file";
	/** URL 协议表示Jar文件: "jar" */
	public static final String URL_PROTOCOL_JAR = "jar";
	/** URL 协议表示zip文件: "zip" */
	public static final String URL_PROTOCOL_ZIP = "zip";
	/** URL 协议表示WebSphere文件: "wsjar" */
	public static final String URL_PROTOCOL_WSJAR = "wsjar";
	/** URL 协议表示JBoss zip文件: "vfszip" */
	public static final String URL_PROTOCOL_VFSZIP = "vfszip";
	/** URL 协议表示JBoss文件: "vfsfile" */
	public static final String URL_PROTOCOL_VFSFILE = "vfsfile";
	/** URL 协议表示JBoss VFS资源: "vfs" */
	public static final String URL_PROTOCOL_VFS = "vfs";
	/** Jar路径以及内部文件路径的分界符: "!/" */
	public static final String JAR_URL_SEPARATOR = "!/";
	/** WAR路径及内部文件路径分界符 */
	public static final String WAR_URL_SEPARATOR = "*/";

	/**
	 * 验证是否为URL
	 * 
	 * @param value
	 *            值
	 * @return 是否为URL
	 */
	public static boolean isUrl(String value) {

		if (StringUtil.isEmpty(value)) {
			return false;
		}
		try {
			new java.net.URL(value);
		} catch (MalformedURLException e) {
			return false;
		}
		return true;
	}

	/**
	 * 通过一个字符串形式的URL地址创建URL对象
	 * 
	 * @param url
	 *            URL
	 * @return URL对象
	 */
	public static URL url(String url) {

		if (StringUtil.isEmpty(url)) {
			return null;
		}

		// 兼容Spring的ClassPath路径
		if (url.startsWith(CLASSPATH_URL_PREFIX)) {
			url = url.substring(CLASSPATH_URL_PREFIX.length());
			return ResourceUtil.getURL(url);
		}

		try {
			return new URL(url);
		} catch (MalformedURLException e) {
			// 尝试文件路径
			try {
				return new File(url).toURI().toURL();
			} catch (MalformedURLException e2) {
				e2.printStackTrace();
			}
		}
		return null;
	}

	/**
	 * 通过一个字符串形式的URL地址创建URL对象
	 * 
	 * @param url
	 *            URL
	 * @param handler
	 *            {@link URLStreamHandler}
	 * @return URL对象
	 */
	public static URL url(String url, URLStreamHandler handler) {

		// 兼容Spring的ClassPath路径
		if (url.startsWith(CLASSPATH_URL_PREFIX)) {
			url = url.substring(CLASSPATH_URL_PREFIX.length());
			return ResourceUtil.getClassLoader(null).getResource(url);
		}

		try {
			return new URL(null, url, handler);
		} catch (MalformedURLException e) {
			// 尝试文件路径
			try {
				return new File(url).toURI().toURL();
			} catch (MalformedURLException e2) {
				e.printStackTrace();
			}
		}
		return null;
	}

	/**
	 * 获得URL，常用于使用绝对路径时的情况
	 * 
	 * @param file
	 *            URL对应的文件对象
	 * @return URL
	 * @exception UtilException
	 *                MalformedURLException
	 */
	public static URL getURL(File file) {

		if (file == null) {
			return null;
		}
		try {
			return file.toURI().toURL();
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}

	/**
	 * 格式化URL链接
	 * 
	 * @param url
	 *            需要格式化的URL
	 * @return 格式化后的URL，如果提供了null或者空串，返回null
	 */
	public static String formatUrl(String url) {

		if (StringUtil.isEmpty(url)) {
			return null;
		}
		if (url.startsWith("http://") || url.startsWith("https://")) {
			return url;
		}
		return "http://" + url;
	}

	/**
	 * 补全相对路径
	 * 
	 * @param baseUrl
	 *            基准URL
	 * @param relativePath
	 *            相对URL
	 * @return 相对路径
	 * @exception UtilException
	 *                MalformedURLException
	 */
	public static String complateUrl(String baseUrl, String relativePath) {

		baseUrl = formatUrl(baseUrl);
		if (StringUtil.isEmpty(baseUrl)) {
			return null;
		}

		try {
			final URL absoluteUrl = new URL(baseUrl);
			final URL parseUrl = new URL(absoluteUrl, relativePath);
			return parseUrl.toString();
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;

	}

	/**
	 * 编码URL，默认使用UTF-8编码<br>
	 * 将需要转换的内容（ASCII码形式之外的内容），用十六进制表示法转换出来，并在之前加上%开头。
	 * 
	 * @param url
	 *            URL
	 * @return 编码后的URL
	 * @exception UtilException
	 *                UnsupportedEncodingException
	 * @since 3.1.2
	 */
	public static String encode(String url) {

		return encode(url, IOUtil.DEFAULT_CHARSET);
	}

	/**
	 * 编码URL<br>
	 * 将需要转换的内容（ASCII码形式之外的内容），用十六进制表示法转换出来，并在之前加上%开头。
	 * 
	 * @param url
	 *            URL
	 * @param charset
	 *            编码
	 * @return 编码后的URL
	 * @exception UtilException
	 *                UnsupportedEncodingException
	 */
	public static String encode(String url, String charset) {

		try {
			return URLEncoder.encode(url, charset);
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
		}
		return null;
	}

	/**
	 * 解码URL<br>
	 * 将%开头的16进制表示的内容解码。
	 * 
	 * @param url
	 *            URL
	 * @return 解码后的URL
	 * @exception UtilException
	 *                UnsupportedEncodingException
	 * @since 3.1.2
	 */
	public static String decode(String url) {

		return decode(url, IOUtil.DEFAULT_CHARSET);
	}

	/**
	 * 解码URL<br>
	 * 将%开头的16进制表示的内容解码。
	 * 
	 * @param url
	 *            URL
	 * @param charset
	 *            编码
	 * @return 解码后的URL
	 * @exception UtilException
	 *                UnsupportedEncodingException
	 */
	public static String decode(String url, String charset) {

		try {
			return URLDecoder.decode(url, charset);
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
		}
		return null;
	}

	/**
	 * 获得path部分<br>
	 * 
	 * @param uriStr
	 *            URI路径
	 * @return path
	 * @exception UtilException
	 *                包装URISyntaxException
	 */
	public static String getPath(String uriStr) {

		URI uri = null;
		try {
			uri = new URI(uriStr);
			return uri.getPath();
		} catch (URISyntaxException e) {
			e.printStackTrace();
		}
		return null;
	}

	/**
	 * 从URL对象中获取不被编码的路径Path<br>
	 * 对于本地路径，URL对象的getPath方法对于包含中文或空格时会被编码，导致本读路径读取错误。<br>
	 * 此方法将URL转为URI后获取路径用于解决路径被编码的问题
	 * 
	 * @param url
	 *            {@link URL}
	 * @return 路径
	 * @since 3.0.8
	 */
	public static String getDecodedPath(URL url) {

		String path = null;
		// URL对象的getPath方法对于包含中文或空格的问题
		path = URLUtil.toURI(url).getPath();
		return (null != path) ? path : url.getPath();
	}

	/**
	 * 转URL为URI
	 * 
	 * @param url
	 *            URL
	 * @return URI
	 * @exception UtilException
	 *                包装URISyntaxException
	 */
	public static URI toURI(URL url) {

		if (null == url) {
			return null;
		}
		try {
			return url.toURI();
		} catch (URISyntaxException e) {
			e.printStackTrace();
		}
		return null;
	}

	/**
	 * 转字符串为URI
	 * 
	 * @param location
	 *            字符串路径
	 * @return URI
	 * @exception UtilException
	 *                包装URISyntaxException
	 */
	public static URI toURI(String location) {

		try {
			return new URI(location.replace(" ", "%20"));
		} catch (URISyntaxException e) {
			e.printStackTrace();
		}
		return null;
	}

	/**
	 * 提供的URL是否为文件<br>
	 * 文件协议包括"file", "vfsfile" 或 "vfs".
	 * 
	 * @param url
	 *            {@link URL}
	 * @return 是否为文件
	 * @since 3.0.9
	 */
	public static boolean isFileURL(URL url) {

		String protocol = url.getProtocol();
		return (URL_PROTOCOL_FILE.equals(protocol) || //
				URL_PROTOCOL_VFSFILE.equals(protocol) || //
				URL_PROTOCOL_VFS.equals(protocol));
	}

	/**
	 * 提供的URL是否为jar包URL 协议包括： "jar", "zip", "vfszip" 或 "wsjar".
	 * 
	 * @param url
	 *            {@link URL}
	 * @return 是否为jar包URL
	 */
	public static boolean isJarURL(URL url) {

		final String protocol = url.getProtocol();
		return (URL_PROTOCOL_JAR.equals(protocol) || //
				URL_PROTOCOL_ZIP.equals(protocol) || //
				URL_PROTOCOL_VFSZIP.equals(protocol) || //
				URL_PROTOCOL_WSJAR.equals(protocol));
	}

	/**
	 * 将URL字符串转换为URL对象，并做必要验证
	 * 
	 * @param urlStr
	 *            URL字符串
	 * @return URL
	 */
	public static URL toUrlForHttp(String urlStr) {

		return toUrlForHttp(urlStr, null);
	}

	/**
	 * 将URL字符串转换为URL对象，并做必要验证
	 * 
	 * @param urlStr
	 *            URL字符串
	 * @param handler
	 *            {@link URLStreamHandler}
	 * @return URL
	 */
	public static URL toUrlForHttp(String urlStr, URLStreamHandler handler) {

		// 去掉url中的空白符，防止空白符导致的异常
		urlStr = StringUtil.cleanBlank(urlStr);
		return URLUtil.url(urlStr, handler);
	}

	/**
	 * 标准化URL字符串，包括：
	 * 
	 * <pre>
	 * 1. 多个/替换为一个
	 * </pre>
	 * 
	 * @param url
	 *            URL字符串
	 * @param isEncodeBody
	 *            是否对URL中body部分的中文和特殊字符做转义（不包括http:和/）
	 * @return 标准化后的URL字符串
	 * @since 4.4.1
	 */
	public static String normalize(String url) {

		if (StringUtil.isEmpty(url)) {
			return url;
		}
		final int sepIndex = url.indexOf("://");
		String pre;
		String body;
		if (sepIndex > 0) {

			pre = url.substring(0, sepIndex + 3);
			body = url.substring(sepIndex + 3, url.length());
		} else {
			pre = "http://";
			body = url;
		}

		final int paramsSepIndex = body.indexOf('?');
		String params = null;
		if (paramsSepIndex > 0) {
			params = body.substring(paramsSepIndex, body.length());
			body = body.substring(0, paramsSepIndex);
		}

		// 去除开头的\或者/
		body = body.replaceAll("^[\\/]+", StringUtil.EMPTY);
		// 替换多个\或/为单个/
		body = body.replace("\\", "/").replaceAll("//+", "/");
		return pre + body + StringUtil.nullToEmpty(params);
	}

	/**
	 * 将表单数据加到URL中（用于GET表单提交）<br>
	 * 表单的键值对会被url编码，但是url中原参数不会被编码
	 * 
	 * @param url
	 *            URL
	 * @param form
	 *            表单数据
	 * @param charset
	 *            编码
	 * @param isEncodeParams
	 *            是否对键和值做转义处理
	 * @return 合成后的URL
	 */
	public static String urlWithForm(String url, Map<String, Object> form, Charset charset, boolean isEncodeParams) {

		if (isEncodeParams && url.indexOf("?") > 0) {
			// 在需要编码的情况下，如果url中已经有部分参数，则编码之
			// url = encodeParams(url, charset);
		}

		// url和参数是分别编码的
		return urlWithForm(url, toParams(form, charset), charset, false);
	}

	/**
	 * 将表单数据字符串加到URL中（用于GET表单提交）
	 * 
	 * @param url
	 *            URL
	 * @param queryString
	 *            表单数据字符串
	 * @param charset
	 *            编码
	 * @param isEncode
	 *            是否对键和值做转义处理
	 * @return 拼接后的字符串
	 */
	public static String urlWithForm(String url, String queryString, Charset charset, boolean isEncode) {

		if (StringUtil.isEmpty(queryString)) {
			// 无额外参数
			// if (StringUtil.contains(url, '?')) {
			// // url中包含参数
			// return isEncode ? encodeParams(url, charset) : url;
			// }
			return url;
		}

		// 始终有参数
		final StringBuilder urlBuilder = new StringBuilder();
		urlBuilder.append(url);
		int qmIndex = url.indexOf('?');
		if (qmIndex > 0) {
			// 原URL带参数，则对这部分参数单独编码（如果选项为进行编码）
			if (!url.endsWith("&")) {
				// 已经带参数的情况下追加参数
				urlBuilder.append('&');
			}
		} else {
			// 原url无参数，则不做编码
			// urlBuilder.append(url);
			if (qmIndex < 0) {
				// 无 '?' 追加之
				urlBuilder.append('?');
			}
		}
		urlBuilder.append(queryString);
		return urlBuilder.toString();
	}

	/**
	 * 将Map形式的Form表单数据转换为Url参数形式，不做编码
	 * 
	 * @param paramMap
	 *            表单数据
	 * @return url参数
	 */
	public static String toParams(Map<String, ?> paramMap) {

		return toParams(paramMap, IOUtil.CHARSET_UTF_8);
	}

	/**
	 * 将Map形式的Form表单数据转换为Url参数形式<br>
	 * 编码键和值对
	 * 
	 * @param paramMap
	 *            表单数据
	 * @param charsetName
	 *            编码
	 * @return url参数
	 */
	public static String toParams(Map<String, Object> paramMap, String charsetName) {

		return toParams(paramMap, Charset.forName(charsetName));
	}

	/**
	 * 将Map形式的Form表单数据转换为Url参数形式<br>
	 * paramMap中如果key为空（null和""）会被忽略，如果value为null，会被做为空白符（""）<br>
	 * 会自动url编码键和值
	 * 
	 * <pre>
	 * key1=v1&amp;key2=&amp;key3=v3
	 * </pre>
	 * 
	 * @param paramMap
	 *            表单数据
	 * @param charset
	 *            编码
	 * @return url参数
	 */
	public static String toParams(Map<String, ?> paramMap, Charset charset) {

		if (paramMap == null) {
			return StringUtil.EMPTY;
		}
		if (null == charset) {// 默认编码为系统编码
			charset = IOUtil.CHARSET_UTF_8;
		}

		final StringBuilder sb = new StringBuilder();
		boolean isFirst = true;
		String key;
		Object value;
		String valueStr;
		for (Entry<String, ?> item : paramMap.entrySet()) {
			if (isFirst) {
				isFirst = false;
			} else {
				sb.append("&");
			}
			key = item.getKey();
			value = item.getValue();
			if (StringUtil.isNotEmpty(key)) {
				sb.append(key).append("=");
				valueStr = String.valueOf(value);
				if (StringUtil.isNotEmpty(valueStr)) {
					sb.append(valueStr);
				}
			}
		}
		return sb.toString();
	}
}
